package io.cici.cc.mybatis.plus.core.metadata;

import io.cici.cc.mybatis.lite.builder.MapperBuilder;
import io.cici.cc.mybatis.lite.executor.keygen.KeyGenerator;
import io.cici.cc.mybatis.lite.executor.keygen.SelectKeyGenerator;
import io.cici.cc.mybatis.lite.mapping.MappedStatement;
import io.cici.cc.mybatis.lite.mapping.ResultMappings;
import io.cici.cc.mybatis.lite.mapping.SqlCommandType;
import io.cici.cc.mybatis.lite.reflection.Reflector;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.sql_source.StaticSqlSource;
import io.cici.cc.mybatis.lite.type.SimpleTypeRegistry;
import io.cici.cc.mybatis.plus.annotation.DbType;
import io.cici.cc.mybatis.plus.annotation.Id;
import io.cici.cc.mybatis.plus.annotation.KeySequence;
import io.cici.cc.mybatis.plus.annotation.Table;
import io.cici.cc.mybatis.plus.core.config.GlobalConfig;
import io.cici.cc.mybatis.plus.core.incrementer.IKeyGenerator;
import io.cici.cc.mybatis.plus.core.toolkit.*;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static java.util.stream.Collectors.toList;

@Slf4j
@Setter
@Getter
public class TableInfoHelper {


    /**
     * 储存反射类表信息
     */
    private static final Map<Class<?>, TableInfo> TABLE_INFO_CACHE = new ConcurrentHashMap<>();

    /**
     * 储存表名对应的反射类表信息
     */
    private static final Map<String, TableInfo> MAP = new ConcurrentHashMap<>();

    /**
     * 默认表主键名称
     */
    private static final String DEFAULT_ID_NAME = "id";

    /**
     * <p>
     * 获取实体映射表信息
     * </p>
     *
     * @param clazz 反射实体类
     * @return 数据库表反射信息
     */
    public static TableInfo getTableInfo(Class<?> clazz) {
        if (clazz == null || clazz.isPrimitive() || SimpleTypeRegistry.isSimpleType(clazz) || clazz.isInterface()) {
            return null;
        }
        // https://github.com/baomidou/mybatis-plus/issues/299
        Class<?> targetClass = ClassUtils.getUserClass(clazz);
        TableInfo tableInfo = TABLE_INFO_CACHE.get(targetClass);
        if (null != tableInfo) {
            return tableInfo;
        }
        // 尝试获取父类缓存
        Class<?> currentClass = clazz;
        while (null == tableInfo && Object.class != currentClass) {
            currentClass = currentClass.getSuperclass();
            tableInfo = TABLE_INFO_CACHE.get(ClassUtils.getUserClass(currentClass));
        }
        // 把父类的移到子类中来
        if (tableInfo != null) {
            TABLE_INFO_CACHE.put(targetClass, tableInfo);
        }
        return tableInfo;
    }

    /**
     * <p>
     * 根据表名获取实体映射表信息
     * </p>
     *
     * @param tableName 表名
     * @return 数据库表反射信息
     */
    public static TableInfo getTableInfo(String tableName) {
        if (StringUtils.isBlank(tableName)) {
            return null;
        }
        return MAP.get(tableName);
    }


    /**
     * <p>
     * 实体类反射获取表信息【初始化】
     * </p>
     *
     * @param clazz 反射实体类
     * @return 数据库表反射信息
     */
    public static synchronized TableInfo initTableInfo(MapperBuilder mapperBuilder, Class<?> clazz) {
        TableInfo targetTableInfo = TABLE_INFO_CACHE.get(clazz);
        final Configuration configuration = mapperBuilder.getConfiguration();
        if (targetTableInfo != null) {
            Configuration oldConfiguration = targetTableInfo.getConfiguration();
            if (!oldConfiguration.equals(configuration)) {
                // 不是同一个 Configuration,进行重新初始化
                targetTableInfo = initTableInfo(configuration, mapperBuilder.getNamespace(), clazz);
            }
            return targetTableInfo;
        }
        return initTableInfo(configuration, mapperBuilder.getNamespace(), clazz);
    }


    private static synchronized TableInfo initTableInfo(Configuration configuration, String currentNamespace,
                                                        Class<?> clazz) {
        /* 没有获取到缓存信息,则初始化 */
        TableInfo tableInfo = new TableInfo(configuration, clazz);
        tableInfo.setCurrentNamespace(currentNamespace);
        GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);

        /* 初始化表名相关 */
        initTableName(clazz, globalConfig, tableInfo);



        /* 初始化字段相关 */
        initTableFields(configuration, clazz, globalConfig, tableInfo);

        /* 自动构建 resultMap */
        tableInfo.initResultMapIfNeed();

        TABLE_INFO_CACHE.put(clazz, tableInfo);
        MAP.put(tableInfo.getTableName(), tableInfo);


        return tableInfo;
    }

    private static void initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {

        Table table = clazz.getAnnotation(Table.class);
        String tableName = table.value();
        tableInfo.setTableName(tableName);
        /* 表结果集映射 */
        if (StringUtils.isNotBlank(table.resultMap())) {
            tableInfo.setResultMap(table.resultMap());
        }
        tableInfo.setAutoInitResultMap(table.autoResultMap());
        /* 开启了自定义 KEY 生成器 */
        KeySequence keySequenceAnnotation = clazz.getAnnotation(KeySequence.class);
        if (keySequenceAnnotation != null) {
            tableInfo.setKeySequence(keySequenceAnnotation);
        }
    }


    private static void initTableFields(Configuration configuration, Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {


        Reflector reflector = tableInfo.getReflector();
        List<Field> list = getAllFields(clazz);
        // 标记是否读取到主键
        boolean isReadPK = false;
        // 是否存在 @TableId 注解
        boolean existTableId = isExistTableId(list);

        List<Column> fieldList = new ArrayList<>(list.size());
        for (Field field : list) {
            boolean isPK = false;

            /* 主键ID 初始化 */
            if (existTableId) {
                Id id = field.getAnnotation(Id.class);
                if (id != null) {
                    if (isReadPK) {
                        throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", clazz.getName());
                    }

                    initTableIdWithAnnotation(globalConfig, tableInfo, field, id);
                    isPK = isReadPK = true;
                }
            } else if (!isReadPK) {
                isPK = isReadPK = initTableIdWithoutAnnotation(globalConfig, tableInfo, field);
            }


            final io.cici.cc.mybatis.plus.annotation.Column column = field.getAnnotation(io.cici.cc.mybatis.plus.annotation.Column.class);

            /* 有 @TableField 注解的字段初始化 */
            if (column != null) {
                Column tableFieldInfo = new Column(globalConfig, tableInfo, field, column,
                        reflector);
                fieldList.add(tableFieldInfo);

                continue;
            }

            /* 无 @TableField 注解的字段初始化 */
            Column tableFieldInfo = new Column(globalConfig, tableInfo, field, reflector);
            fieldList.add(tableFieldInfo);

        }

        /* 字段列表 */
        tableInfo.setFieldList(fieldList);

        /* 未发现主键注解，提示警告信息 */
        if (!isReadPK) {
            log.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName()));
        }
    }


    public static boolean isExistTableId(List<Field> list) {
        return list.stream().anyMatch(field -> field.getAnnotation(Id.class) != null);
    }


    private static void initTableIdWithAnnotation(GlobalConfig globalConfig, TableInfo tableInfo, Field field,
                                                  Id id) {
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        boolean underCamel = tableInfo.isUnderCamel();
        final String property = field.getName();
        if (field.getAnnotation(io.cici.cc.mybatis.plus.annotation.Column.class) != null) {
            log.warn(String.format(
                    "This \"%s\" is the table primary key by @TableId annotation in Class: \"%s\",So @TableField annotation will not work!",
                    property, tableInfo.getEntityType().getName()));
        }
        /* 主键策略（ 注解 > 全局 ） */
        // 设置 Sequence 其他策略无效
        tableInfo.setIdType(id.type());

        /* 字段 */
        String column = property;
        if (StringUtils.isNotBlank(id.value())) {
            column = id.value();
        } else {
            // 开启字段下划线申明
            if (underCamel) {
                column = StringUtils.camelToUnderline(column);
            }
            // 全局大写命名
            if (dbConfig.isCapitalMode()) {
                column = column.toUpperCase();
            }
        }
        final Class<?> keyType = tableInfo.getReflector().getGetterType(property);
        if (keyType.isPrimitive()) {
            log.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", property,
                    tableInfo.getEntityType().getName()));
        }
        if (StringUtils.isEmpty(id.value())) {
            String columnFormat = dbConfig.getColumnFormat();
            if (StringUtils.isNotBlank(columnFormat)) {
                column = String.format(columnFormat, column);
            }
        }
        tableInfo.setKeyRelated(checkRelated(underCamel, property, column)).setKeyColumn(column)
                .setKeyProperty(property).setKeyType(keyType);
    }


    private static boolean initTableIdWithoutAnnotation(GlobalConfig globalConfig, TableInfo tableInfo, Field field) {
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        final String property = field.getName();
        if (DEFAULT_ID_NAME.equalsIgnoreCase(property)) {
            if (field.getAnnotation(io.cici.cc.mybatis.plus.annotation.Column.class) != null) {
                log.warn(String.format(
                        "This \"%s\" is the table primary key by default name for `id` in Class: \"%s\",So @TableField will not work!",
                        property, tableInfo.getEntityType().getName()));
            }
            String column = property;
            if (dbConfig.isCapitalMode()) {
                column = column.toUpperCase();
            }
            final Class<?> keyType = tableInfo.getReflector().getGetterType(property);
            if (keyType.isPrimitive()) {
                log.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"",
                        property, tableInfo.getEntityType().getName()));
            }
            String columnFormat = dbConfig.getColumnFormat();
            if (StringUtils.isNotBlank(columnFormat)) {
                column = String.format(columnFormat, column);
            }
            tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), property, column))
                    .setIdType(dbConfig.getIdType()).setKeyColumn(column).setKeyProperty(property).setKeyType(keyType);
            return true;
        }
        return false;
    }


    public static boolean checkRelated(boolean underCamel, String property, String column) {
        column = StringUtils.getTargetColumn(column);
        String propertyUpper = property.toUpperCase(Locale.ENGLISH);
        String columnUpper = column.toUpperCase(Locale.ENGLISH);
        if (underCamel) {
            // 开启了驼峰并且 column 包含下划线
            return !(propertyUpper.equals(columnUpper)
                    || propertyUpper.equals(columnUpper.replace(StringPool.UNDERSCORE, StringPool.EMPTY)));
        } else {
            // 未开启驼峰,直接判断 property 是否与 column 相同(全大写)
            return !propertyUpper.equals(columnUpper);
        }
    }


    public static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz));
        return fieldList.stream().filter(field -> {
            /* 过滤注解非表字段属性 */
            io.cici.cc.mybatis.plus.annotation.Column column = field.getAnnotation(io.cici.cc.mybatis.plus.annotation.Column.class);
            return (column == null);
        }).collect(toList());
    }

    public static KeyGenerator genKeyGenerator(String baseStatementId, TableInfo tableInfo, MapperBuilder mapperBuilder) {
        List<IKeyGenerator> keyGenerators = GlobalConfigUtils.getKeyGenerators(mapperBuilder.getConfiguration());
        if (CollectionUtils.isEmpty(keyGenerators)) {
            throw new IllegalArgumentException("not configure IKeyGenerator implementation class.");
        }
        IKeyGenerator keyGenerator = null;
        if (keyGenerators.size() > 1) {
            // 多个主键生成器
            KeySequence keySequence = tableInfo.getKeySequence();
            if (null != keySequence && DbType.OTHER != keySequence.dbType()) {
                keyGenerator = keyGenerators.stream().filter(k -> k.dbType() == keySequence.dbType()).findFirst()
                        .orElse(null);
            }
        }
        // 无法找到注解指定生成器，默认使用第一个生成器
        if (null == keyGenerator) {
            keyGenerator = keyGenerators.get(0);
        }
        Configuration configuration = mapperBuilder.getConfiguration();
        String id = mapperBuilder.getNamespace() + StringPool.DOT + baseStatementId
                + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        ResultMappings resultMappings = new ResultMappings.Builder(mapperBuilder.getConfiguration(), id, tableInfo.getKeyType(),
                new ArrayList<>()).build();
        MappedStatement mappedStatement = new MappedStatement.Builder(mapperBuilder.getConfiguration(), id,
                new StaticSqlSource(configuration, keyGenerator.executeSql(tableInfo.getKeySequence().value())),
                SqlCommandType.SELECT).keyProperty(tableInfo.getKeyProperty()).resultMaps(resultMappings).build();
        configuration.addMappedStatement(mappedStatement);
        return new SelectKeyGenerator(mappedStatement, true);
    }

}
